/**********************************************************************

  Ruby.NET Runtime Library
  Originally developed at Queensland University of Technology
 
  Some sections of this C# code mirror the structure of the C code in the
  Ruby 1.8.2 Interpreter Copyright (C) 1993-2003 Yukihiro Matsumoto, et.al.
  
**********************************************************************/

using Ruby.Runtime;
using Ruby;
using System.Collections.Generic;
using System.Text;
using Microsoft.Build.Utilities;
using gppg;

namespace Ruby.Compiler
{
    using AST;


    public class YYLTYPE : IMerge<YYLTYPE>
    {
        public int first_line;
        public int first_column;
        public int last_line;
        public int last_column;
        public string file;

        internal YYLTYPE()
        {
        }

        internal YYLTYPE(string file, int first_line, int first_column, int last_line, int last_column)
        {
            this.file = file;
            this.first_line = first_line;
            this.first_column = first_column;
            this.last_line = last_line;
            this.last_column = last_column;
        }


        public YYLTYPE Merge(YYLTYPE last)
        {
            if (last == null)
                return this;

            YYLTYPE merged = new YYLTYPE();
            merged.first_column = this.first_column;
            merged.first_line = this.first_line;
            merged.last_column = last.last_column;
            merged.last_line = last.last_line;
            merged.file = this.file;
            return merged;
        }

        public override string ToString()
        {
            return "\"" + file + "\" line " + first_line.ToString() + ", column " + first_column.ToString();
        }
    }

    // The non-autogenerated part of the Parser class.

    public partial class Parser
    {
        private int in_single = 0;
        private int in_def = 0;

        internal Scope eval_tree;
        internal Scope eval_tree_begin;


        public static SOURCEFILE ParseFile(string filename, IO file, int start)
        {
            Parser parser = new Parser();
            parser.scanner = new Scanner(parser, file);
            SOURCEFILE tree = (SOURCEFILE)parser.ParseInput(filename, start);

            if (parser.scanner.errors > 0 && Compiler.log != null)
                throw new SyntaxError("Syntax error").raise(null);

            return tree;
        }

        public static SOURCEFILE ParseStream(System.IO.TextReader codeStream)
        {
            Parser parser = new Parser();
            parser.scanner = new Scanner(parser, codeStream);
            SOURCEFILE tree = (SOURCEFILE)parser.ParseInput("filename", 1);
            return tree;
        }

        public static Scope ParseString(Frame caller, Frame scope, string filename, String s, int line)
        {
            Parser parser = new Parser();
            parser.scanner = new Scanner(parser, s);

            if (scope != null)
                parser.CreateContext(scope);

            Scope tree = parser.ParseInput(filename, line);

            if (parser.scanner.errors > 0)
                throw new SyntaxError("compile error").raise(caller);

            return tree;
        }


        private Scope ParseInput(string filename, int line)
        {
            //System.IO.FileInfo file = new System.IO.FileInfo(f);
            scanner.sourcefile = filename;
            scanner.sourceline = scanner.start_line = line-1;

            bool ok = Parse();

            eval_tree.BEGIN = eval_tree_begin;

            return eval_tree;
        }


        // ------------------------------------------------------------------------


        internal void CreateContext(Frame innerFrame)
        {
            List<Frame> outer = innerFrame.OuterFrames();
            outer.Reverse();

            foreach (Frame frame in outer)
            {
                //System.Console.WriteLine("Outer Frame {0}", frame);
                if (frame.current_block != null)
                    enter_scope(new BLOCK(CurrentScope, null));
                else
                    enter_scope(new DEFN(CurrentScope, frame.methodName(), null, null));

                CurrentScope.frame_type = frame.GetType();

                foreach (System.Reflection.FieldInfo localField in frame.GetType().GetFields())
                    if (localField.DeclaringType == frame.GetType())
                        CurrentScope.locals_list.Add(localField.Name);
            }

            //System.Console.WriteLine("innerFrame Frame {0}", innerFrame);
            enter_scope(new EVAL(CurrentScope, innerFrame, null));
            CurrentScope.frame_type = innerFrame.GetType();
            foreach (System.Reflection.FieldInfo localField in innerFrame.GetType().GetFields())
                if (localField.DeclaringType == innerFrame.GetType())
                    CurrentScope.locals_list.Add(localField.Name);            
        }



        // ------------------------------------------------------------------------

        private Stack<Scope> scope_stack = new Stack<Scope>();

        private Scope enter_scope(Scope scope)
        {
            scope_stack.Push(scope);
            return scope;
        }

        private Scope leave_scope(YYLTYPE location, params object[] args)
        {
            Scope currentScope = scope_stack.Pop();
            if (args.Length > 0)
                currentScope.Init(location, args);
            return currentScope;
        }

        internal Scope CurrentScope
        {
            get
            {
                if (scope_stack.Count > 0)
                    return scope_stack.Peek();
                else
                    return null;
            }
        }

        private Node Concat(Node a, Node b)
        {
            if (a is VALUE && (string)(((VALUE)a).value) == "")
                return b;
            else
                return new CONCAT(a, b, a.location.Merge(b.location));
        }


        private Node gettable(string id, YYLTYPE location)
        {
            if (id == ID.intern(Tokens.kSELF))
                return new SELF(location);
            else if (id == ID.intern(Tokens.kNIL))
                return new NIL(location);
            else if (id == ID.intern(Tokens.kTRUE))
                return new TRUE(location);
            else if (id == ID.intern(Tokens.kFALSE))
                return new FALSE(location);
            else if (id == ID.intern(Tokens.k__FILE__))
                return new VALUE(scanner.sourcefile, location);
            else if (id == ID.intern(Tokens.k__LINE__))
                return new VALUE(scanner.sourceline, location);
            else if (ID.Scope(id) == ID_Scope.LOCAL)
            {
                if (CurrentScope.has_local(id))
                    return CurrentScope.add_local(id, location);
                else
                    return new METHOD_CALL(id, new ARGS(location, true), true, location);
            }
            else if (ID.Scope(id) == ID_Scope.GLOBAL)
                return new GVAR(id, location);
            else if (ID.Scope(id) == ID_Scope.INSTANCE)
                return new IVAR(id, location);
            else if (ID.Scope(id) == ID_Scope.CONST)
                return new CONST(CurrentScope, id, location);
            else if (ID.Scope(id) == ID_Scope.CLASS)
                return new CVAR(CurrentScope, id, location);
            else
            {
                scanner.yyerror("identifier {0} is not valid", id.ToString());
                return null;
            }
        }

        private LVALUE assignable(string id, YYLTYPE location)
        {
            if (id == ID.intern(Tokens.kSELF))
                scanner.yyerror("Can't change the value of self");
            else if (id == ID.intern(Tokens.kNIL))
                scanner.yyerror("Can't assign to nil");
            else if (id == ID.intern(Tokens.kTRUE))
                scanner.yyerror("Can't assign to true");
            else if (id == ID.intern(Tokens.kFALSE))
                scanner.yyerror("Can't assign to false");
            else if (id == ID.intern(Tokens.k__FILE__))
                scanner.yyerror("Can't assign to __FILE__");
            else if (id == ID.intern(Tokens.k__LINE__))
                scanner.yyerror("Can't assign to __LINE__");
            else if (ID.Scope(id) == ID_Scope.LOCAL)
                return CurrentScope.add_local(id, location);
            else if (ID.Scope(id) == ID_Scope.GLOBAL)
                return new GVAR(id, location);
            else if (ID.Scope(id) == ID_Scope.INSTANCE)
                return new IVAR(id, location);
            else if (ID.Scope(id) == ID_Scope.CONST)
                return new CONST(CurrentScope, id, location);
            else if (ID.Scope(id) == ID_Scope.CLASS)
                return new CVAR(CurrentScope, id, location);
            else
                scanner.yyerror("identifier {0} is not valid", id.ToString());

            return null;
        }

        internal static Node append(Node a, Node b)
        {
            if (a == null) return b;
            if (b == null) return a;

            Node last = a;
            while (last.nd_next != null)
            {
                if (last == b) throw new System.NotImplementedException("append cycle");
                last = last.nd_next;
            }

            last.nd_next = b;

            return a;
        }

        private Node append(params Node[] list)
        {
            Node a = list[0];

            for (int i = 1; i < list.Length; i++)
                a = append(a, list[i]);

            return a;
        }


        private void backref_error(Node node)
        {
            if (node is NTH_REF)
                scanner.yyerror("Cant set variable ${0}", ((NTH_REF)node).nth);
            else if (node is BACK_REF)
                scanner.yyerror("Cant set variable ${0}", (char)((BACK_REF)node).ch);
        }


        private Node negate_lit(VALUE node)
        {
            if (node.value is int)
                node.value = -(int)(node.value);
            else if (node.value is double)
                node.value = -(double)(node.value);
            else if (node.value is BigNum)
                ((BigNum)node.value).sign *= -1;
            else
                throw new System.NotImplementedException("negate_lit" + node.value.GetType());

            return node;
        }


        internal new Scanner scanner
        {
            get { return (Scanner)base.scanner; }
            set { base.scanner = value; }
        }
    }
}
